/*
 * Copyright (c) 2023 Huawei Technologies Co., Ltd.
 * openUBMC is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *         http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 */
 
#include <iomanip>
#include <string>
#include <sstream>
#include "ncsi_over_mctp_mellanox.h"
#include <ncsi/ncsi_protocol.h>

#include "mc/dict.h"
#include "mc/variant.h"
#include "mc/log.h"

namespace dev {
bool check_response_data(oem_mlnx_common_rsp_t* rsp, uint8_t command_id, uint8_t parameter) {
    uint16_t response_code = SHORT_BY_BIG_ENDIAN(rsp->response_code);
    uint16_t reason_code   = SHORT_BY_BIG_ENDIAN(rsp->reason_code);

    if(response_code != RESP_CODE_SUCCESS && reason_code != REASON_CODE_SUCCESS){
        ilog("response data with response code: ${response_code}, "
            "reason code: ${reason_code}",
            ("response_code", response_code)("reason_code", reason_code));
        return false;
    }
    
    if (rsp->manufacturer_id != MELLANOX_MANUFACTURE_ID_REP) {
        ilog("invalid response data with wrong manufacturer id: ${manufacturer_id}",
             ("manufacturer_id", rsp->manufacturer_id));
        return false;
    }
    if (rsp->command_id != command_id) {
        ilog("invalid response data with wrong sub command id: ${command_id}",
             ("command_id", rsp->command_id));
        return false;
    }

    if (rsp->parameter != parameter) {
        ilog("invalid response data with wrong parameter: ${parameter}",
             ("parameter", rsp->parameter));
        return false;
    }
    return true;
}

mc::dict get_oem_command_ctx_mellanox(uint8_t channel_id, uint8_t package_id)
{
    mc::dict ctx({
        {"PacketType", OEM_COMMAND},
        {"ExpectRspPacketType", OEM_COMMAND_RSP},
        {"ChannelId", channel_id},
        {"PackageId", package_id}
    });
    return ctx;
}

bool ncsi_over_mctp_mellanox::get_temp_info(uint8_t package_id, uint8_t channel_id,  uint8_t temp_type, ncsi_temp_info_t *temp_info)
{
    if (temp_info == nullptr) {
        ilog("Error: temp info pointer is null");
        return false;
    }
    mc::dict ctx = get_oem_command_ctx_mellanox(channel_id, package_id);

    std::vector<uint8_t> data =
        MELLANOX_COMMON_DATA(OEM_MLNX_GET_TEMP_CMD, OEM_MLNX_GET_TEMP_PARA, temp_type);
    
    std::string                   rsp_str;
    bool                          ret = request(ctx, data, rsp_str);
    if (!ret) {
        ilog("Error: failed to send NCSI get temp info command");
        return false;
    }
    
    if (rsp_str.empty() || rsp_str.size() < OEM_MLNX_TEMP_RSP_MIN_LEN) {
        ilog("get temp info rsp is empty or size(${rsp_size}) is less than ${size}",
             ("rsp_size", rsp_str.size())("size", OEM_MLNX_TEMP_RSP_MIN_LEN));
        return false;
    }
    
    oem_mlnx_temp_rsp_t* rsp = nullptr;
    rsp = reinterpret_cast<oem_mlnx_temp_rsp_t*>(const_cast<char*>(rsp_str.data()));
    if (!check_response_data(reinterpret_cast<oem_mlnx_common_rsp_t *>(rsp), OEM_MLNX_GET_TEMP_CMD, OEM_MLNX_GET_TEMP_PARA)) {
        return false;
    }
    
    temp_info->max_operating_temp = rsp->max_operating_temp; 
    temp_info->cur_temp = rsp->cur_temp;    
    return true;
}

bool ncsi_over_mctp_mellanox::get_mac_addr(uint8_t package_id, uint8_t channel_id, uint8_t *mac_addr, uint8_t len)
{
    if (mac_addr == nullptr) {
        ilog("Error: mac address pointer is null");
        return false;
    }
    mc::dict ctx = get_oem_command_ctx_mellanox(channel_id, package_id);
    
    std::vector<uint8_t> data =
        MELLANOX_COMMON_DATA(OEM_MLNX_GET_PF_MAC_CMD, OEM_MLNX_GET_PF_MAC_PARA, 0x00);

    std::string rsp_str;
    bool ret = request(ctx, data, rsp_str);
    if (!ret) {
        return false;
    }
    if (rsp_str.empty() || rsp_str.size() != OEM_MLNX_MAC_ADDR_RSP_LEN) {
        ilog("get mac rsp is empty or size(${rsp_size}) is not equal to ${size}",
             ("rsp_size", rsp_str.size())("size", OEM_MLNX_MAC_ADDR_RSP_LEN));
        return false;
    }
    
    oem_mlnx_mac_addr_rsp_t* rsp = reinterpret_cast<oem_mlnx_mac_addr_rsp_t*>(const_cast<char*>(rsp_str.data()));
    if (!check_response_data(reinterpret_cast<oem_mlnx_common_rsp_t *>(rsp), OEM_MLNX_GET_PF_MAC_CMD, OEM_MLNX_GET_PF_MAC_PARA)) {
        return false;
    }

    memcpy(mac_addr, rsp->mac_addr, len);    
    return true;
}

bool ncsi_over_mctp_mellanox::get_default_mac_addr(uint8_t package_id, uint8_t channel_id, uint8_t *mac_addr, uint8_t len)
{
    if (mac_addr == nullptr) {
        ilog("Error: mac address pointer is null");
        return false;
    }
    mc::dict ctx = get_oem_command_ctx_mellanox(channel_id, package_id);
    
    std::vector<uint8_t> data =
        MELLANOX_COMMON_DATA(OEM_MLNX_GET_PORT_ID_CMD, OEM_MLNX_GET_PORT_ID_PARA, 0x00);

    std::string rsp_str;
    bool ret = request(ctx, data, rsp_str);
    if (!ret) {
        ilog("Error: failed to send NCSI get default MAC address command");
        return false;
    }
    if (rsp_str.empty() || rsp_str.size() != OEM_MLNX_PORT_ID_RSP_LEN) {
        ilog("get mac rsp is empty or size(${rsp_size}) is not equal to ${size}",
             ("rsp_size", rsp_str.size())("size", OEM_MLNX_PORT_ID_RSP_LEN));
        return false;
    }
    
    oem_mlnx_port_id_rsp_t* rsp = reinterpret_cast<oem_mlnx_port_id_rsp_t*>(const_cast<char*>(rsp_str.data()));
    if (!check_response_data(reinterpret_cast<oem_mlnx_common_rsp_t *>(rsp), OEM_MLNX_GET_PORT_ID_CMD, OEM_MLNX_GET_PORT_ID_PARA)) {
        return false;
    }

    memcpy(mac_addr, rsp->mac_addr, len);    
    return true;
}

bool ncsi_over_mctp_mellanox::reset_nic(uint8_t package_id, uint8_t channel_id)
{
    mc::dict ctx = get_oem_command_ctx_mellanox(channel_id, package_id);

    std::vector<uint8_t> data =
        MELLANOX_COMMON_DATA(OEM_MLNX_RESET_NCI_CMD, OEM_MLNX_RESET_NIC_PARA, 0x02);
    
    std::string                   rsp_str;
    bool                          ret = request(ctx, data, rsp_str);
    if (!ret) {
        ilog("Error: failed to send NCSI reset nic command");
        return false;
    }
    
    if (rsp_str.empty() || rsp_str.size() != OEM_MLNX_RESET_NIC_LEN) {
        ilog("reset nic rsp is empty or size(${rsp_size}) is not equal to ${size}",
             ("rsp_size", rsp_str.size())("size", OEM_MLNX_RESET_NIC_LEN));
        return false;
    }
    
    oem_mlnx_reset_nic_rsp_t* rsp = nullptr;
    rsp = reinterpret_cast<oem_mlnx_reset_nic_rsp_t*>(const_cast<char*>(rsp_str.data()));
    if (!check_response_data(reinterpret_cast<oem_mlnx_common_rsp_t *>(rsp), OEM_MLNX_RESET_NCI_CMD, OEM_MLNX_RESET_NIC_PARA)) {
        return false;
    }
 
    return true;
}

bool ncsi_over_mctp_mellanox::get_lldp_capability(uint8_t package_id, uint8_t channel_id, uint8_t *lldp_capability)
{
    if (lldp_capability == nullptr) {
        ilog("Error: lldp info pointer is null");
        return false;
    }
    mc::dict ctx = get_oem_command_ctx_mellanox(channel_id, package_id);

    std::vector<uint8_t> data =
        MELLANOX_COMMON_DATA(OEM_MLNX_GET_LLDP_CMD, OEM_MLNX_GET_LLDP_PARA, 0x00);
    
    std::string                   rsp_str;
    bool                          ret = request(ctx, data, rsp_str);
    if (!ret) {
        ilog("Error: failed to send NCSI get lldp command");
        return false;
    }
    
    if (rsp_str.empty() || rsp_str.size() != OEM_MLNX_LLDP_RSP_LEN) {
        ilog("get lldp rsp is empty or size(${rsp_size}) is not equal to ${size}",
             ("rsp_size", rsp_str.size())("size", OEM_MLNX_LLDP_RSP_LEN));
        return false;
    }
    
    oem_mlnx_lldp_rsp_t* rsp = nullptr;
    rsp = reinterpret_cast<oem_mlnx_lldp_rsp_t*>(const_cast<char*>(rsp_str.data()));
    if (!check_response_data(reinterpret_cast<oem_mlnx_common_rsp_t *>(rsp), OEM_MLNX_GET_LLDP_CMD, OEM_MLNX_GET_LLDP_PARA)) {
        return false;
    }
    
    *lldp_capability = ((rsp->lldp_tx_mode) ? 1 : 0);  
    return true;
}

bool ncsi_over_mctp_mellanox::set_lldp_capability(uint8_t package_id, uint8_t channel_id, uint8_t lldp_capability)
{
    mc::dict ctx = get_oem_command_ctx_mellanox(channel_id, package_id);

    uint8_t lldp_data = (lldp_capability << 4) & 0x10;
    std::vector<uint8_t> data =
        MELLANOX_COMMON_DATA(OEM_MLNX_SET_LLDP_CMD, OEM_MLNX_SET_LLDP_PARA, 0x00);
    std::vector<uint8_t> req_data = {0x00, 0x00, 0x00, lldp_data};
    data.insert(data.end(), req_data.begin(), req_data.end());
    
    std::string                   rsp_str;
    bool                          ret = request(ctx, data, rsp_str);
    if (!ret) {
        ilog("Error: failed to send NCSI set lldp command");
        return false;
    }
    
    if (rsp_str.empty() || rsp_str.size() != OEM_MLNX_LLDP_RSP_LEN) {
        ilog("get lldp rsp is empty or size(${rsp_size}) is not equal to ${size}",
             ("rsp_size", rsp_str.size())("size", OEM_MLNX_LLDP_RSP_LEN));
        return false;
    }
    
    oem_mlnx_lldp_rsp_t* rsp = nullptr;
    rsp = reinterpret_cast<oem_mlnx_lldp_rsp_t*>(const_cast<char*>(rsp_str.data()));
    if (!check_response_data(reinterpret_cast<oem_mlnx_common_rsp_t *>(rsp), OEM_MLNX_SET_LLDP_CMD, OEM_MLNX_SET_LLDP_PARA)) {
        return false;
    } 
    return true;
}

} // namespace dev 